﻿SB_DKP = {}

-- Numbered tier descriptions. Eases up things.
	-- Tiers 4 and 5.
	SB_DKP.Tier = {}
SB_DKP.Tier.Defender = 1
SB_DKP.Tier.Champion = 2
SB_DKP.Tier.Hero = 3
	-- Tier 6
SB_DKP.Tier.Vanquisher = 4
SB_DKP.Tier.Conqueror = 5
SB_DKP.Tier.Protector = 6

-- Hard-coded for now, we need a more dynamic loot filter.
-- ALL VARIABLES FOLLOWING SHOULD BE SAVED TO THE WTF FOLDER ---
SB_DKP.Loot = {}
SB_DKP.Loot.Ignore = {}
SB_DKP.Loot.Ignore.Threshold = 3
SB_DKP.Loot.Ignore.Name = {}
	SB_DKP.Loot.Ignore.Name["Badge of Justice"] = true
SB_DKP.Loot.Ignore.ItemID = {}

SB_DKP_DEBUG = 0

SB_DKP.Loot.Session = {}
SB_DKP.Loot.Counter = 1
----------------------------------------------------------------

SB_DKP.DropDown = {}

SB_DKP.Raid = {} -- fill this thing up with the current raid for MUCH easier acces than WoW API allows :-P. Me wuv tables.

SB_DKP.CommandList = {} -- list of the commands available. It's with two values, the command name and its associated function call.

SB_DKP.UI = {} -- container for UI functions and variables.

SB_DKP.UI.ResultsLatest = {} -- table to always contain the latest results... used by the UI.
SB_DKP.UI.Current = {} -- new table to always containt the latest results... the point right now is to maintain a slight bit of backwards viewability.

-- Table for item qualities.
-- THESE VALUES ARE DRAWN FROM WOWWIKI.COM AND SEEM TO HAVE VERY LITTLE BASIS IN THE ACTUAL GAME.
-- CORRECT: Poor, Common, Uncommon
-- WRONG: Rare (0080ff), Epic (b048f8)
-- UNKNOWN: Legendary
SB_DKP.ItemQuality =
{
		{
			"Poor",
			"9d9d9d"
		},
		{
			"Common",
			"ffffff"
		},
		{
			"Uncommon",
			"1eff00"
		},
		{
			"Rare",
			"0070dd"
		},
		{
			"Epic",
			"a335ee"
		},
		{
			"Legendary",
			"ff8000"
		}
}

--[[

NOTE: A serious code clean up is required as several features now counteract each other. This is most obvious in the whisper responses and slash command parsers.
NOTE2: Doubly so on the clean up order. I've started the second iteration, SB_DKP, to signify the jump from text-based to GUI-based.

WORKS:
	- Anonymous chat response - double responses should be eliminated now! I just hope that the events are handled sequencially.
		- A few bugs exist in the anonymous responses. When debugging is enabled, certain addons can reveal the query's origin with their own chat hooks.
	- Self slash command.
	- Local search and return.
	- Advanced functions and unicode support (special characters).
	- Local internal get functions for searches/queries.
	- Search chat commands.
	- Slash command parsing for several commands.
	- Simple debugging to a high degree. Note that you should still use something like !swatter for syntax and code errors.
	- Graphics. Frames are up, SimpleHTML is working. The basic plan is in place. Search works and special functions too.

WHAT IS PLANNED:
	- Item links.
	- Loot tracking, including DKP price tags.
	- Bidding.

WIP:
	- Massive job: dropdown searches. A lot of these can be easily fitted to match in code. I'd really like to clean them up for some custom filters, though.
		- Whole raid/party: done.
		- Free-text search: done.
		- Tier 4/5 and 6 (current toon): done.
		% Tier 4/5 and 6 (any class) - raid/party: NOT DONE.
		% Tier 4/5 and 6 - guild: NOT DONE.
		- Classes (raid): done.
	- Function name cleanup. Function names need to follow a more logical naming convention (i.e. GetChar and CharRename should be straightenede out, for example to start with "Char" to define their working domain).
	% Saving of data between sessions (and for export): underway. THIS IS PISSING ME OFF!
	- XML cleanup. The code made by the AddOn Studio is not perfectly clean and could be improved upon slightly by a human touch. I think that SB_DKP_Frame.xml should remain untouched from the visual designer from here on out, to avoid further code contamination.

NEEP IMPLEMENTATION (design is OK):
	- Look tracking: Well underway. Currently, loot itemID, name, looter and an optional price is tagged in the SB_DKP.Loot.Session table. I'm still working on session saving and some functions to make this easier.

--]]

function SB_DKP:OnLoad()
	this:RegisterEvent("CHAT_MSG_LOOT")

--[[ Saved variables are being fucking ignored right now... fucking WoW.
	local tempTimestamp = time() -- temporary timestamp, to ensure milliseconds don't fuck it up.
	 SB_DKP_Saved = {}
	 SB_DKP_Saved["Code"] = "default"
	if (SB_DKP_Saved == nil) then
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("It would seem the internal variables are empty")
		end
		SB_DKP_Saved = {}
		SB_DKP_Saved["Timestamp"] = tempTimestamp -- we'll push in a timestamp to help us check data vailidity at a later time.
		SB_DKP_Saved["Loaded"] = false
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Issued timestamp "..SB_DKP_Saved["Timestamp"]..".")
		end
	else
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("There are already variables. Working with timestamp "..SB_DKP_Saved["Timestamp"]..".")
			SB_DKP:Print(SB_DKP_Saved["Timestamp"].." was found. Current timestamp is "..tempTimestamp..".")
		end
		SB_DKP_Saved["Loaded"] = false
	end
--]]
	-- Just check if there is actually data to pull from.
	if (SB_DKP_DATA == nil) then
		-- If there is no database, error the user and kill self, i.e. do not do shit.
		self:PrintError("Error! No database variable! Stopped loading!")
		return
	end
	-- If everything checks out, register necessary variables.
	SLASH_SB_DKP1 = "/sbdkp"
	SlashCmdList["SB_DKP"] = SB_DKP_Command
	self:CommandInit()
end

function SB_DKP:CommandInit() -- this command initialises all available commands in the SB_DKP.CommandList table.
	SB_DKP.CommandList["tier"] = SB_DKP.CmdPrintTierX
	SB_DKP.CommandList["debug"] = SB_DKP.CmdDebugToggle
	SB_DKP.CommandList["show"] = SB_DKP.CmdShow
	SB_DKP.CommandList["hide"] = SB_DKP.CmdHide
	SB_DKP.CommandList["size"] = SB_DKP.CmdUIResize
end

function SB_DKP:IsCommand(NameOfCmd)
	if (SB_DKP_DEBUG >= 1) then
		self:Print("IsCommand: Looking for '"..NameOfCmd.."'.")
	end
	if(SB_DKP.CommandList[NameOfCmd] == nil) then
		if (SB_DKP_DEBUG >= 1) then
			self:Print("IsCommand: '"..NameOfCmd.."' not found.")
		end
		return false
	else
		if (SB_DKP_DEBUG >= 1) then
			self:Print("IsCommand: '"..NameOfCmd.."' found.")
		end
		return true
	end
end

function SB_DKP:OnEvent(event)
	if (event == "RAID_ROSTER_UPDATE") then -- If we recieve an update in the raid, update the raid DB.
		self:RaidDBUpdate() -- refill the entire raid. Seems like the fastest method, seriously. A max of 25 calls to a shitload of conditionals.
	end
end

function SB_DKP:RaidDBUpdate() --  refills the whole SB_DKP.Raid table with fresh data. May be CPU heavy. UPDATE: seems very quickly finished.
	SB_DKP.Raid = {} -- reset the table, i.e. empty it.
	local rarg1, rarg2, rarg3, rarg4, rarg5, rarg6, rarg7, rarg8, rarg9, rarg10, rarg11
	local tmpTable = {}
	for iterator = 1, GetNumRaidMembers(), 1 do
		rarg1, rarg2, rarg3, rarg4, rarg5, rarg6, rarg7, rarg8, rarg9, rarg10, rarg11 = GetRaidRosterInfo(iterator)
		tmpTable = {rarg1, rarg2, rarg3, rarg4, rarg5, rarg6, rarg7, rarg8, rarg9, rarg10, rarg11}
		if (SB_DKP_DEBUG >= 1) then
			self:Print("RaidDBUpdate: Now inserting '"..tmpTable[1].."'.")
		end
		tinsert(SB_DKP.Raid, tmpTable)
	end
end

-- Finds a character in the raid and returns his raid index. The function forces a raid update in order to secure perfect sync.
function SB_DKP:RaidGetChar(name)
	self:RaidDBUpdate()
	for index, value in ipairs(SB_DKP.Raid) do
		if (strlower(name) == strlower(value[1])) then
			if (SB_DKP_DEBUG >= 1) then
				self:Print("Found "..value[1].." in "..index..".")
			end
			return index
		end
	end
end

function SB_DKP:GetRaidClasses(printbool) -- prints the amount of each class in the raid.
	local numRaiders = GetNumRaidMembers()
	if (numRaiders == 0) then
		self:Print("Not in a raid!")
		return nil
	end
	local tmpDebug = SB_DKP_DEBUG
	SB_DKP_DEBUG = 0
	SB_DKP:RaidDBUpdate() -- update the raid db for fresh information.
	SB_DKP_DEBUG = tmpDebug
	local tmpClassTable = {Druid = {"Druid", 0}, Warrior = {"Warrior", 0}, Warlock = {"Warlock", 0}, Shaman = {"Shaman", 0}, Rogue = {"Rogue", 0}, Mage = {"Mage", 0}, Paladin = {"Paladin", 0}, Priest = {"Priest", 0}, Hunter = {"Hunter", 0}}
	if (SB_DKP_DEBUG >= 1) then
		SB_DKP:Print("Counting classes now.")
	end
	for index,char in ipairs(SB_DKP.Raid) do
		tmpClassTable[tostring(char[5])][2] = tmpClassTable[tostring(char[5])][2] + 1
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Added 1 "..char[5])
		end
	end
	if (SB_DKP_DEBUG >= 1) then
		SB_DKP:Print("Printing...")
	end
	if (printbool == true) then
		for index,class in pairs(tmpClassTable) do
			self:Print(class[1]..": "..class[2])
		end
	end
	return tmpClassTable
end

function SB_DKP:Whisper(target, message)
	SendChatMessage(message, "WHISPER", nil, target)
end

function SB_DKP:IsEven(number) -- determines whether the parameter number is even (true) or uneven (false). Used to limit chat spammage.
	--[[ Simple theory. Should the floored value of half the number be less than half the number, we have an uneven number.
	
	Example: 87, which is... obviously... uneven.
	87 / 2 = 43.5
	floor(43.5) = 43
	43 < 43.5 is TRUE. We could ceiling it as well, but never mind that detail.
	
	--]]
	local TmpChar = number / 2
	if (SB_DKP_DEBUG >= 1) then
		self:Print("TmpChar is "..TmpChar..". floor(TmpChar) is "..floor(TmpChar)..".")
	end
	if (floor(TmpChar) < TmpChar) then
		return false
	else
		return true
	end
end

local SB_CHAT_COUNTER = 0
local SB_SPAM_COUNTER = 0

local ORIG_ChatFrame_MessageEventHandler = ChatFrame_MessageEventHandler
-- HOOKED CHAT FUNCTION
function ChatFrame_MessageEventHandler(event)
	if (event == "CHAT_MSG_WHISPER_INFORM" and strlower(strsub(arg1, 1, 4)) == "@dkp") then
		-- do nothing if you're telling someone else something with dkp.
		return
	end
	if (event == "CHAT_MSG_WHISPER" and strlen(arg1) < 5 and strlower(strsub(arg1, 1, 4)) == "!dkp" and arg2 == UnitName("player") and (SB_DKP_DEBUG >= 1) == false) then
		SB_SPAM_COUNTER = SB_SPAM_COUNTER + 1
		if (SB_DKP:IsEven(SB_SPAM_COUNTER)) then
			if (SB_DKP_DEBUG >= 1) then
				SB_DKP:Print("Double chat has been removed!")
			end
			return
		end
		SB_DKP:Print("Can't query yourself, dumbass!")
		return nil
	end	
	-- if it's a DKP request, act on it.
	if (event == "CHAT_MSG_WHISPER" and strlen(arg1) < 5 and strlower(strsub(arg1, 1, 4)) == "!dkp") then
		SB_SPAM_COUNTER = SB_SPAM_COUNTER + 1
		if (SB_DKP:IsEven(SB_SPAM_COUNTER)) then -- if it's an even number, assume second response... ignore it.
			if (SB_DKP_DEBUG >= 1) then
				SB_DKP:Print("Double chat has been removed!")
			end
			return
		end
		SB_CHAT_COUNTER = SB_CHAT_COUNTER + 1
		-- ignore it, but respond
		SB_DKP:Print("DKP requested. Count "..SB_CHAT_COUNTER)
		local FoundChar = SB_DKP:GetChar(arg2, "equals")
		if (FoundChar == nil) then -- if we don't find an exact, we didn't find it at all - bail out.
				SB_DKP:Whisper("@dkp - Jeg kunne ikke finde dig i databasen - beklager.")
				return nil
		end
		SB_DKP:Whisper(arg2, "@dkp - "..FoundChar[1].." ("..FoundChar[2]..")")
		return
	end
	-- If it's a detailed DKP request, act differently on it.
	if (event == "CHAT_MSG_WHISPER" and strlen(arg1) > 4 and strlower(strsub(arg1, 1, 4)) == "!dkp") then
		local ArgTable = {}
		SB_SPAM_COUNTER = SB_SPAM_COUNTER + 1
		if (SB_DKP:IsEven(SB_SPAM_COUNTER)) then -- if it's an even number, assume second response... ignore it.
			if (SB_DKP_DEBUG >= 1) then
				SB_DKP:Print("Double chat has been removed!")
			end
			return
		end
		SB_CHAT_COUNTER = SB_CHAT_COUNTER + 1
		if (SB_DKP_DEBUG >= 1) then 
			SB_DKP:Print("Assuming non-singular query.")
		end
		local ArgString = strsub(arg1,6) -- cut away the first five characters, presumably "!dkp ", exactly.
		ArgString = strtrim(ArgString)
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Query is '"..ArgString.."'")
		end

--[[ START PARSING ]]--

		ArgString = ArgString.." " -- add a white space to the end. This is to simplify the while loop.
		local temparg -- used for debugging
		while (strfind(ArgString, " ") ~= nil) do -- until we know there are no more whitespaces, do the parsing.
			if (SB_DKP_DEBUG >= 1) then
				SB_DKP:Print("Current piece is '"..ArgString.."' - extracting "..strsub(ArgString, 1, strfind(ArgString, " ")-1).." now.")
			end
			table.insert(ArgTable, strlower(strsub(ArgString, 1, strfind(ArgString, " ")-1))) -- insert into argtable the string from character 1 up to, and including, the character right before the whitespace) - we turn it lowercase to ease later use of the args.
			if (SB_DKP_DEBUG >= 1) then
				SB_DKP:Print("Reducing fragment to '"..strsub(ArgString, strfind(ArgString, " ")+1).."'.")
			end
			ArgString = strsub(ArgString, strfind(ArgString, " ")+1) -- cut away the used parts of the string
		end
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("argtable contains:")
			for index,argument in ipairs(ArgTable) do
				SB_DKP:Print(index..": "..argument)
			end
		end

--[[ STOP PARSING ]]--

		-- ADD IF CONDITION! IF ArgTable CONTAINS NO ENTIRES (i.e. EMPTY) RUN THE SIMPLE DKP REQUEST. OTHERWISE, START PARSING!
		--- THIS PART OF THE CODE _IS_ BROKEN!

		if (#ArgTable == 0) then
			-- do simple query.
			return nil
		else
			-- do complex query, regardless of contents.
			-- if (SB_DKP:IsCommand(ArgTable[1]) then
			-- the line above uses a non-implemented function. It's to check whether the first argument is a usable command or not.
			-- else
			-- otherwise, we assume a name search.
			local CharIndex = {}
			CharIndex = SB_DKP:GetChar(ArgTable[1], "contains")
			SB_DKP:Print("CharIndex has "..#(CharIndex).." values.")
			if (CharIndex == nil) then
				SB_DKP:Whisper(arg2, "Kan ikke finde en toon med '"..ArgTable[1].."'!")
				return nil
			elseif (#(CharIndex) > 1) then -- if we get more than one hit, start listing.
				local AnswerString = ""
				for index,CharData in ipairs(CharIndex) do
					AnswerString = "@dkp - "..CharData[1].." ("..CharData[2]..")"
					SB_DKP:Whisper(arg2, AnswerString)
				end
				return true
			elseif (#(CharIndex) == 1) then -- if one hit...
				local AnswerString = ""
				AnswerString = "@dkp - "..CharIndex[1][1].." ("..CharIndex[1][2]..")"
				SB_DKP:Whisper(arg2, AnswerString)
				return true
			else -- if we get here, something is REALLY wrong.
				SB_DKP:Print("ERROR in whisper! Unknown return!")
				local AnswerString = ""
				AnswerString = "@dkp - Error! Unknown error. Ask " .. UnitName("player") .. " what the problem may be."
				return true
			end
		end
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Assuming simple non-singular query")
		end
		local Results = SB_DKP:GetChar(ArgString, "contains")
		if (Results == nil) then
			SB_DKP:Whisper(arg2, "@dkp - Beklager, jeg kunne ikke finde nogen der passede søgningen.")
			return nil
		end
	end
	ORIG_ChatFrame_MessageEventHandler(event);
end

function SB_DKP_Command(command)
	if (SB_DKP_DEBUG >= 1) then
		SB_DKP:Print("Slash!".." ("..command..").")
	end
	if (command == "") then -- if the command value is empty, then toggle the frame. If the frame is hidden, show it with the current character as search query.
		if (SB_DKP_TempFrame:IsShown()) then -- if it's visible, hide it.
			SB_DKP_TempFrame:Hide()
		else
			SB_DKP_TempFrame:Show()
			SB_DKP_SearchBox:SetText(UnitName("player"))
			SB_DKP:UISearch()
			SB_DKP_SearchBox:SetText("")
		end
	--[[ 1.0 Code - OLD
		local found = SB_DKP:GetChar(UnitName("player"), "equals")
		if (found == nil) then
			SB_DKP:Print("Du lader ikke til at være i databasen")
			return
		else
			SB_DKP:Print("Du har " .. found[2] .. "dkp.")
		end
	--]]
	end
-- ADD PARSING
	local argtable = {} -- we initiate a table for use for all the command arguments.
	command = strtrim(command) -- trim white spaces in either end of the command.
	command = command.." " -- add a white space to the end. This is to simplify the while loop.
	local temparg -- used for debugging
	while (strfind(command, " ") ~= nil) do -- until we know there are no more whitespaces, do the parsing.
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Current piece is '"..command.."' - extracting "..strsub(command, 1, strfind(command, " ")-1).." now.")
		end
		table.insert(argtable, strlower(strsub(command, 1, strfind(command, " ")-1))) -- insert into argtable the string from character 1 up to, and including, the character right before the whitespace) - we turn it lowercase to ease later use of the args.
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Reducing fragment to '"..strsub(command, strfind(command, " ")+1).."'.")
		end
		command = strsub(command, strfind(command, " ")+1) -- cut away the used parts of the string
	end
	if (SB_DKP_DEBUG >= 1) then
		SB_DKP:Print("argtable contains:")
		for index,argument in ipairs(argtable) do
			SB_DKP:Print(index..": "..argument)
		end
	end
-- END PARSING - artable can now be passed as the set of arguments.
	-- At this point, we kind of KNOW that the command string wasn't empty. We don't care about extra parameters, because we've already parsed the contents.
	if (SB_DKP:IsCommand(strlower(argtable[1]))) then -- if it's a known command, react to it.
		if (SB_DKP_DEBUG >= 1) then
			SB_DKP:Print("Executing "..argtable[1]..".")
		end
		local CallFunc = SB_DKP.CommandList[strlower(argtable[1])]
		CallFunc(argtable) -- execute the command. DEBUG SAFETIES HAVE NOT BEEN IMPLEMENTED (whispers may cause debugging on/off).
		return true
	end
	-- If we get to this point, assume character modifier command, e.g. Mazyridon -100 will subtract 100 dkp.
	if (#(argtable) == 2) then -- if we have exactly 2 values in the table, proceed to character mod.
		local GottenChar = SB_DKP:GetChar(argtable[1], "index_exact") -- Retrieve index of exact character match. Expect nil if nothing found.
		if (GottenChar == nil) then -- if we found no exact match (returns nil) go here.
			SB_DKP:Print("Could not find exact match '"..argtable[1].."'.")
			return nil
		else
			if (tonumber(argtable[2]) == nil) then -- first of all, check for a faulty return value from tonumber().
				SB_DKP:Print("Faulty argument #2!")
				return nil
			elseif (tonumber(argtable[2]) < 0) then -- if it's a negative, do the subtraction
				SB_DKP_DATA[GottenChar][2] = SB_DKP_DATA[GottenChar][2] - tonumber(strsub(argtable[2], 2)) -- extract the positive value and subtract it... complexity to avoid problems.
			elseif (tonumber(argtable[2]) > 0) then -- if it's greater than, it's an addition.
				SB_DKP_DATA[GottenChar][2] = SB_DKP_DATA[GottenChar][2] + tonumber(argtable[2]) -- add the number in argument 2.
			elseif (tonumber(argtable[2]) == 0) then -- if it's zero, someone's an idiot.
				SB_DKP:Print("Zero value given in argument #2... what the hell?")
			end
			return 1 -- return 1, for succesful execution, in all other cases.
		end
	end
-- end here if we get no hits - assume character search.
	if (#(argtable) == 1 and strlen(argtable[1])>0) then -- only act on a non-empty #1 parameter - it may be empty if self-query.
		if (SB_DKP_DEBUG >= 1) then
		SB_DKP:Print("Presuming search. Sending "..argtable[1].." to PrintChar")
		end
		SB_DKP:PrintChar(argtable[1],"contains")
		return 1 -- return success... whatever.
	end
end

-- Function to secure easy future-reliance (extension into addon log)
function SB_DKP:Print(message)
	local prefix = "SB_DKP: "
	local result = prefix..message
	DEFAULT_CHAT_FRAME:AddMessage(result)
end

-- Error printing function - just so people get it.
function SB_DKP:PrintError(message)
	local prefix = "SB_DKP: "
	local result = prefix..message
	DEFAULT_CHAT_FRAME:AddMessage(result, 0.8, 0, 0)
end

-- Works like a console front-end to GetChar - it parses the results. This minimizes code reuse for the better.
function SB_DKP:PrintChar(ToFind, SearchMode)
	if (SearchMode == nil or ToFind == nil) then
		self:Print("Faulty params! Nil values were passed!")
		return nil
	end
	if (strlower(SearchMode) == "contains") then
		local Results = self:GetChar(ToFind, SearchMode)
		if (Results == nil) then
			self:Print("No matches were found!")
			return nil
		end
		self:Print("Found:")
		for index,chardata in ipairs(Results) do
			self:Print(index..": "..chardata[1].." ("..chardata[2]..")")
		end
		return
	end
	if (strlower(SearchMode) == "equals") then
		local Result = self:GetChar(ToFind, SearchMode)
		if (Result == nil) then
			self:Print("No match was found!")
			return nil
		else
			self:Print(Result[1].." ("..Result[2]..")")
			return
		end
	end
	-- At this point, presume faulty arguments.
	self:Print("Faulty arguments! Did you use either 'start', 'contains' or 'equals'?")
	return nil
end

-- Same as GetChar, but uses a unicode-compatible database.
function SB_DKP:GetChar(ToFind, SearchMode)
	if (SearchMode == nil) then
		SB_DKP:Print("Search mode not defined. Call the method with either 'starts', 'contains' or 'equals'")
		return nil
	end
	ToFind = strtrim(ToFind) -- remove any spaces in the query - we do not like spaces.
	local hits = 0 -- number of found hits.
	if (strlower(SearchMode) == "start") then -- if the optional argument is "start" i.e. the string is in the beginning of the word.
		local CharacterTable = {}
		for index,chardata in ipairs(SB_DKP_DATA) do -- strfind will return nil if it does not find anything - and if the first character is further than the first space, it is not at the beginning.
			-- test to see if the start of the string is to be found at all.
			if (strfind(strlower(chardata[1]), strlower(ToFind)) ~= 1) then
				-- bail out if you find nothing.
			end
			if (strfind(strlower(chardata[1]), strlower(ToFind)) == 1) then
				if (SB_DKP_DEBUG >= 1) then
					self:Print(chardata[1] .. "(" .. chardata[2] .. ")")
				end
				table.insert(CharacterTable, chardata)
				hits = hits + 1
			end
		end
		return CharacterTable
	end
	if (strlower(SearchMode) == "contains") then -- simple "contains" search.
		local CharacterTable = {} -- table to store multiple results.
		for index,chardata in ipairs(SB_DKP_DATA) do
			-- In this context, SB_DKP_DATA[index][1] and chardata[1] should be the same data.
			if (strfind(strlower(chardata[1]), strlower(ToFind)) ~= nil) then
				if (SB_DKP_DEBUG >= 1) then
					self:Print(chardata[1] .. "(" .. chardata[2] .. ")")
					hits = hits + 1
				end
				table.insert(CharacterTable, chardata) -- inserts into the position last in the table. Find name with CharacterTable[CharPos][DataPos] - [1][1] gives you the name of the first entry, [1][2] the dkp of same... etc.
			end
		end
		return CharacterTable -- always returns the table.
	end
	if (strlower(SearchMode) == "equals") then -- even simpler search - return exact match, though not case-sensitive.
		for index,chardata in ipairs(SB_DKP_DATA) do
			if (strlower(chardata[1]) == strlower(ToFind)) then
				return chardata -- return if we hit
			end
		end
		-- here, we assume no hits.
		return nil
	end
	if (strlower(SearchMode) == "index_exact") then -- special search mode used by internal functions for easy manipulation.
		for index,chardata in ipairs(SB_DKP_DATA) do
			if (strlower(chardata[1]) == strlower(ToFind)) then
				return index -- return the index should we find the character
			end
		end
		-- at this point, assume no hits.
		return nil
	end
	if (strlower(SearchMode) == "chardata") then -- special internal search mode. Used to find several hits and return them in a new table. Best used when the table needs to be further modified.
		if (SB_DKP_DEBUG >= 1) then
			self:Print(SearchMode .. " initiated succesfully. Debug level 2 or higher required for output.")
		end
		local ReturnTable = {}
		for index,chardata in ipairs(SB_DKP_DATA) do
			if (strfind(strlower(chardata[1]),strlower(ToFind)) ~= nil and strfind(strlower(chardata[1]),"?") == nil) then -- if strfind does NOT return nil on the search string and it DOES return nil on a search for "?" (compatibility check), we have a hit.
				tinsert(ReturnTable, chardata)
				if (SB_DKP_DEBUG > 1) then
					self:Print("Found " .. chardata[1] .. ". Inserted.")
				end
			end
		end
		return ReturnTable
	end
	if (strlower(SearchMode) == "index") then -- special internal search mode. Used when you need reference data and not the data alone. I.e. this mode will return the corresponding indices from SB_DKP_DATA, not their contents (contrary to search mode "chardata")
		if (SB_DKP_DEBUG >= 1) then
			self:Print(SearchMode .. " initiated succesfully. Debug level 2 or higher required for output.")
		end
		local ReturnTable = {}
		for index,chardata in ipairs(SB_DKP_DATA) do
			if (strfind(strlower(chardata[1]),strlower(ToFind)) ~= nil and strfind(strlower(chardata[1]),"?") == nil) then -- if strfind does NOT return nil on the search string and it DOES return nil on a search for "?" (compatibility check), we have a hit.
				tinsert(ReturnTable, index)
				if (SB_DKP_DEBUG > 1) then
					self:Print("Found " .. chardata[1] .. ". Inserted.")
				end
			end
		end
		return ReturnTable
	end
	-- at this point, presume wrong args.
	self:Print("FindChar fault! Have you used correct params?")
end

function SB_DKP:CharRename(FromName, ToName)
	local CharIndex = self:GetChar(FromName, "index_exact")
	if (SB_DKP_DEBUG >= 1) then
		self:Print("Renaming "..FromName.." to "..ToName.."...")
	end
	SB_DKP_DATA[CharIndex][1] = ToName
	if (SB_DKP_DEBUG >= 1) then
		self:Print("Done!")
	end
	return CharIndex
end

-- Prints out found results
function SB_DKP:Search(name)
	local foundchars = {}
	foundchars = SB_DPK:GetChar(name, "contains")
	if (foundchars == nil) then
		self:Print("No hits found!")
	end
	local index = 1
	for name, dkp in pairs(foundchars) do
		self:Print(index .. ": " .. name .. " (" .. dkp .. ").")
		index = index + 1
	end
end

-- ARG: nil = full raid, 1 - 6 tiers 4/5 or 6 groups. See top of this file.
function SB_DKP:RaidGetDKPTier(tiergroup) -- returns a table with SB_DKP_DATA data using the raid index.
	if (SB_DKP_DEBUG >= 1) then
		if (tiergroup == nil) then self:Print("Recieved nil tiergroup parameter")
		else self:Print("Recieved tiergroup parameter '" .. tiergroup .."'.")
		end
		
	end
	local ReturnTable = {}
	local tempchardata
	self:RaidDBUpdate() -- always update to latest raid specs.
	if (tiergroup == nil) then
		for index, chardata in ipairs(self.Raid) do
			if (chardata ~= nil) then -- we WILL get nil data.
				tempchardata = self:GetChar(chardata[1], "equals")
				if (tempchardata == nil) then -- failsafe. Replace a NIL-valued SB_DKP_DATA char with 0 dkp.
					tempchardata = {chardata[1], 0}
				end
				tinsert(ReturnTable, tempchardata) -- insert the SB_DKP_DATA index of the character into the return table.
				if (SB_DKP_DEBUG > 1) then
					self:Print("Inserted " .. tempchardata[1] .. ", dkp " .. tempchardata[2] .. ".")
				end
			end
		end
		return ReturnTable
	end
	if (tiergroup ~= nil) then
		if (SB_DKP.Tier.Defender <= tiergroup and tiergroup <= SB_DKP.Tier.Protector) then
			for index, chardata in ipairs(self.Raid) do
				if (SB_DKP_DEBUG > 1) then
					self:Print("RaidGetDKPTier: Now parsing " .. chardata[1] .. ".")
				end
				if (chardata ~= nil) then -- we WILL get nil data.
					if (self:RaidIsTierGroup(index, tiergroup)) then
						tempchardata = self:GetChar(chardata[1], "equals")
						if (SB_DKP_DEBUG > 1) then
							self:Print("GetChar returned "..#(tempchardata)..".")
						end
						if (tempchardata == nil) then
							tempchardata = {}
							tempchardata[1] = chardata[1]
							tempchardata[2] = 0
						end
						tinsert (ReturnTable, tempchardata)
						if (SB_DKP_DEBUG > 1) then
							self:Print("Added " .. tempchardata[1] .. " (" .. chardata[5] .. ")("..tempchardata[2]..")")
						end
					end
				end
			end
		end
		return ReturnTable
	end
	self:Print("SB_RaidGetDKPTier: Something fucked up!")
	return nil
end

-- Returns true if the character indexed in raidindex in SB_DKP.Raid resides within tiergroup. Can be any of the entries in SB_DKP.Tier (Defender, Champion, etc.)
function SB_DKP:RaidIsTierGroup(raidindex, tiergroup)
	if (SB_DKP_DEBUG > 1) then
		self:Print("RaidIsTierGroup: Recieved " .. self.Raid[raidindex][1] .. " to check against tiergroup " .. tiergroup .. ".")
	end
	local tempclass = strlower(self.Raid[raidindex][5])
	if (tiergroup == SB_DKP.Tier.Defender) then
		if (tempclass == "druid" or tempclass == "priest" or tempclass == "warrior") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	elseif (tiergroup == SB_DKP.Tier.Champion) then
		if (tempclass == "shaman" or tempclass == "rogue" or tempclass == "paladin") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	elseif (tiergroup == SB_DKP.Tier.Hero) then
		if (tempclass == "warlock" or tempclass == "hunter" or tempclass == "mage") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	elseif (tiergroup == SB_DKP.Tier.Vanquisher) then
		if (tempclass == "druid" or tempclass == "mage" or tempclass == "rogue") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	elseif (tiergroup == SB_DKP.Tier.Conqueror) then
		if (tempclass == "paladin" or tempclass == "warlock" or tempclass == "priest") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	elseif (tiergroup == SB_DKP.Tier.Protector) then
		if (tempclass == "shaman" or tempclass == "hunter" or tempclass == "warrior") then
			if (SB_DKP_DEBUG > 1) then
				self:Print("Index " .. raidindex .. " is a " .. tiergroup .. ".")
			end
			return true
		end
	else
		self:Print("RaidIsTierGroup: Faulty parameter! Check your code!")
		return false
	end
	if (SB_DKP_DEBUG > 1) then
		self:Print("RaidIsTierGroup: Reached end with no tier match. Returning false.")
	end
	return false
end

-- Args: 1-9 gives classes, check the numbers on the top of this file.
function SB_DKP:RaidGetDKPClass(class) -- returns a table with SB_DKP_DATA data using the raid index. This function searches for certain class matches.
	if (SB_DKP_DEBUG >= 1) then
		if (class == nil) then
			-- This function does not support full raid searching. We'll split it out soon enough.
			self:PrintError("RaidGetDKPClass: No class defined. Aborting!")
			return 0
		end
	end
	local ReturnTable = {}
	local tempchardata
	if (SB_DKP_DEBUG > 1) then
		self:Print("RaidGetDKPClass: Updating raid DB.")
	end
	self:RaidDBUpdate() -- always update to latest raid specs.
	if (SB_DKP_DEBUG > 1) then
		self:Print("RaidGetDKPClass: Done updating raid DB.")
	end
	for index, chardata in ipairs(self.Raid) do
		if (SB_DKP_DEBUG > 1) then
			self:Print("RaidGetDKPClass: Now parsing " .. chardata[1] .. ".")
		end
		if (chardata ~= nil) then -- we WILL get nil data.
			if (SB_DKP_DEBUG > 1) then
				self:Print("Comparing " .. chardata[1] .. "(" .. chardata[5] .. ") with class " .. class .. ".")
			end
			if (strlower(chardata[5]) == strlower(class)) then
				tempchardata = self:GetChar(chardata[1], "equals")
				if (SB_DKP_DEBUG > 1) then
					self:Print("GetChar returned "..#(tempchardata)..".")
				end
				if (tempchardata == nil) then
					tempchardata = {}
					tempchardata[1] = chardata[1]
					tempchardata[2] = 0
				end
				tinsert (ReturnTable, tempchardata)
				if (SB_DKP_DEBUG > 1) then
					self:Print("Added " .. tempchardata[1] .. " (" .. chardata[5] .. ")("..tempchardata[2]..")")
				end
			end
		end
	end
	return ReturnTable
end

-- This function takes a random string and outputs a more easily usable item object for me.
-- I realise WoW already includes several functions for splitting up this information, but I need so few pieces that this seemed simpler.
function SB_DKP:ItemObjectCreate(itemString)
	if (SB_DKP_DEBUG > 1) then
		self:Print("ItemObjectCreate: Now parsing '" .. itemString .. "'.")
	end
	itemObject = {}
	local itemStringObjectStart = strfind(itemString, "\|", 1, true)
	local _, itemStringObjectEnd = strfind(itemString, "\|r", 1, true == nil) -- ignoring the first returned variable, we're interested in the last position of the itemlink.
	itemString = strsub(itemString, itemStringObjectStart, itemStringObjectEnd) -- trim down to itemlink alone.
	itemObject.Color = strsub(itemString, 5, 10) -- With a pure itemlink, the 5th to 10th character (6 in all) are the color code in hex. Kept in string for simplicity.
	itemString = strsub(itemString, strfind(itemString, ":")+1) -- trim down further, form the start of the item ID.
	local itemID = strsub(itemString, 1, strfind(itemString, ":")-1)
	itemObject.ID = tonumber(itemID)
	itemString = strsub(itemString, strfind(itemString, "[", 1, true), strfind(itemString, "]", 1, true)) -- isolate the final part of the itemlink we want - the name, including brackets.
	itemObject.Name = strsub(itemString, 2, strlen(itemString)-1)
	-- At this point we should have a hex-coded color (6 symbols, 0 to 9, a to f), a pure-integer item ID (unknown length) and the name (proper string).
	if (SB_DKP_DEBUG > 1) then
		self:Print("ItemObjectCreate: Final object contains name, ID, color: " .. itemObject.Name .. "," .. itemObject.ID .. "," .. itemObject.Color .. ".")
	end
	return itemObject
end

-- Function to determine whether a given item name, or itemID, should be ignored by the loot tracker.
function SB_DKP:LootIsIgnored(item)
	if (tonumber(item)) then -- if we CANNOT convert the item to a number, assume name.
		if (SB_DKP_DEBUG >1) then
			self:Print("LootIsIgnored: Searching ignore list by name.")
		end	
		if (SB_DKP.Loot.Ignore.Name[item] == nil) then
			if (SB_DKP_DEBUG >1) then
				self:Print("LootIsIgnored: Did not find name match. Returning false.")
			end
			return false
		else
			if (SB_DKP_DEBUG >1) then
				self:Print("LootIsIgnored: Found name match. Returning true.")
			end
			return true
		end
	else -- if not, assume itemID.
		if (SB_DKP_DEBUG >1) then
			self:Print("LootIsIgnored: Searching ignore list by itemID.")
		end
		for index,key in ipairs(SB_DKP.Loot.Ignore.ItemID) do
		
		end
	end
end

function SB_DKP:LootGetThresholdNumber(item)
	if (item.Color == nil) then
		self:PrintError("LootGetThresholdNumber: Error! Invalid item object passed!")
	end
	if (SB_DKP_DEBUG >1) then
		self:Print("LootGetThresholdNumber: parsing color code " .. item.Color .. ".")
	end
	for index,colorpair in ipairs(SB_DKP.ItemQuality) do
		if (strlower(colorpair[1]) == strlower(item.Color)) then
			if (SB_DKP_DEBUG> 1) then
				self:Print("LootgetThresholdNumber: Found match " .. "(" .. index .. ")!")
			end
			return index
		end
	end
	-- If we get here, something really messed up.
	self:PrintError("LootGetThresholdNumber: Unhandled exception! Tell Mazyridon to check the code again.")
end

function SB_DKP:LootIsTheshold(item)
	if (item.Color == nil) then
		self:PrintError("LootIsThreshold: Error! An item object was not passed!")
		return nil	
	end
	local thresholdColor = SB_DKP.ItemQuality[SB_DKP.Loot.Ignore.Threshold][2] -- find the color code to compare to.
	local thresholdNumber = SB_DKP.Loot.Ignore.Threshold
	local itemColorNumber = self:LootGetThresholdNumber(item)
	if (SB_DKP_DEBUG >1) then
		self:Print("LootIsThreshold: Current threshold is " .. SB_DKP.Loot.Ignore.Threshold .. ", " .. SB_DKP.ItemQuality[SB_DKP.Loot.Ignore.Threshold][1] .. " (" .. SB_DKP.ItemQuality[SB_DKP.Loot.Ignore.Threshold][1] .. ").")
		self:Print("LootIsThreshold: Comparing ITEM (" .. itemColorNumber .. ") with THRESHOLD (" .. thresholdnumber .. ").")
	end
	if (thresholdNumber <= itemColorNumber) then
		return true
	else
		return false
	end
end